package com.katesoft.scale4j.persistent.spring.postprocessor;

import static com.katesoft.scale4j.common.services.IBeanNameReferences.SESSION_FACTORY;
import static com.katesoft.scale4j.common.spring.IPostProcessingOrder.HIBERNATE_SESSION_POST_PROCESSOR;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.propertyeditors.PropertiesEditor;
import org.springframework.core.Ordered;

import com.katesoft.scale4j.common.spring.AbstractBeanPropertiesOverridePostProcessor;
import com.katesoft.scale4j.persistent.hibernate.ICommonHibernateConfiguration;

/**
 * This class allow to change configuration of hibernate session factory bean without changing of
 * original spring configuration.
 * 
 * @author kate2007
 */
public class HibernateSessionExtensionPostProcessor extends
         AbstractBeanPropertiesOverridePostProcessor implements InitializingBean, Ordered,
         ICommonHibernateConfiguration {
   private List<?> mappingResources;
   private List<?> annotatedClasses;
   private List<?> configLocations;
   private Properties hibernateProperties;

   public HibernateSessionExtensionPostProcessor() {
      setTargetBean(SESSION_FACTORY);
      setOrder(HIBERNATE_SESSION_POST_PROCESSOR);
   }

   /**
    * Add the annotated classes and the mapping resources to the existing Session Factory
    * configuration.
    */
   @Override
   @SuppressWarnings({ "unchecked", "rawtypes" })
   protected void doPostProcessing(
            @SuppressWarnings("unused") ConfigurableListableBeanFactory configurableListableBeanFactory) {
      PropertyValue properties = mutablePropertyValues.getPropertyValue("beanProperties");
      Map<Object, Object> definedProperties = (Map<Object, Object>) properties.getValue();
      if (beanProperties != null) {
         definedProperties.putAll(beanProperties);
      }
      if (mappingResources != null) {
         // do we have existing resources?
         PropertyValue propertyValue = mutablePropertyValues.getPropertyValue("mappingResources");
         if (propertyValue == null) {
            propertyValue = new PropertyValue("mappingResources", new ArrayList());
            mutablePropertyValues.addPropertyValue(propertyValue);
         }
         // value is expected to be a list.
         List existingMappingResources = (List) propertyValue.getValue();
         existingMappingResources.addAll(mappingResources);
      }
      if (annotatedClasses != null) {
         // do we have existing resources?
         PropertyValue propertyValue = mutablePropertyValues.getPropertyValue("annotatedClasses");
         if (propertyValue == null) {
            propertyValue = new PropertyValue("annotatedClasses", new ArrayList());
            mutablePropertyValues.addPropertyValue(propertyValue);
         }
         // value is expected to be a list.
         List existingAnnotatedClasses = (List) propertyValue.getValue();
         existingAnnotatedClasses.addAll(annotatedClasses);
      }
      if (configLocations != null) {
         PropertyValue propertyValue = mutablePropertyValues.getPropertyValue("configLocations");
         if (propertyValue == null) {
            propertyValue = new PropertyValue("configLocations", new ArrayList());
            mutablePropertyValues.addPropertyValue(propertyValue);
         }
         List existingConfigLocations = (List) propertyValue.getValue();
         existingConfigLocations.addAll(configLocations);
      }
      if (hibernateProperties != null) {
         PropertyValue propertyValue = mutablePropertyValues
                  .getPropertyValue("hibernateProperties");
         if (propertyValue == null) {
            propertyValue = new PropertyValue("hibernateProperties", hibernateProperties);
            mutablePropertyValues.addPropertyValue(propertyValue);
         } else {
            Properties existingHibernateProperties;
            if (propertyValue.getValue() instanceof Properties) {
               existingHibernateProperties = (Properties) propertyValue.getValue();
            } else {
               PropertiesEditor propertiesEditor = new PropertiesEditor();
               propertiesEditor.setAsText(((TypedStringValue) propertyValue.getValue()).getValue());
               existingHibernateProperties = (Properties) propertiesEditor.getValue();
            }
            existingHibernateProperties.putAll(hibernateProperties);
            mutablePropertyValues.removePropertyValue("hibernateProperties");
            propertyValue = new PropertyValue("hibernateProperties", existingHibernateProperties);
            mutablePropertyValues.addPropertyValue(propertyValue);
         }
      }
   }

   /**
    * Set list of mapping resources (.hbm.xml files) to be added to the session factory.
    * 
    * @param mappingResources
    *           list of mapping resources.
    */
   @Override
   public void setMappingResources(List<?> mappingResources) {
      this.mappingResources = mappingResources;
   }

   /**
    * The list of annotated classes to be added to the session factory.
    * 
    * @param annotatedClasses
    *           list of annotated classes that need to be added.
    */
   @SuppressWarnings("rawtypes")
   @Override
   public void setAnnotatedClasses(List<Class> annotatedClasses) {
      this.annotatedClasses = annotatedClasses;
   }

   /**
    * Set list of configuration locations (i.e. classpath:hibernate.cfg.xml) to be added to the
    * session factory
    * 
    * @param configLocations
    *           The list of configuration locations that need to be added.
    */
   @Override
   public void setConfigLocations(List<?> configLocations) {
      this.configLocations = configLocations;
   }

   /**
    * Hibernate properties to added to the session factory.
    * 
    * @param hibernateProperties
    *           list of additional properties.
    */
   @Override
   public void setHibernateProperties(Properties hibernateProperties) {
      this.hibernateProperties = hibernateProperties;
   }

   @Override
   public void afterPropertiesSet() {
      logger.info(
               "Using SessionFactory = %s, mappingResources = %s, annotatedClasses = %s, configLocations = %s, hibernateProperties = %s",
               getTargetBean(), mappingResources == null ? "[]" : mappingResources.toString(),
               annotatedClasses == null ? "[]" : annotatedClasses.toString(),
               configLocations == null ? "[]" : configLocations.toString(),
               hibernateProperties == null ? "[]" : hibernateProperties.toString());
   }
}
